/*
 * TETRIS for AVR microcontrollers and cascading LED matrix based on MAX7219 driver
 * Created for Interplaymedium™ project (https://interplaymedium.org)
 * Copyright © 2010 Dmitry Shalnov [interplaymedium.org]
 * Licensed under the Apache License, Version 2.0
*/

#include <avr/io.h>
#include <avr/interrupt.h>

// WIRING 
// please change it according to your ARDUINO wiring
// don't forget to place pull-up resisor between RESET and +5V pins to avoid board reset during UART transfer.

#define DATAIN 	3	
#define LOAD 	5	
#define CLOCK 	4	
#define BEEPER 	2	

#define INUSE 	1	// how many matrix you connect
#define STARTX	4	// horisontal position od figure strat
#define RAND_MAX   4

// Some macros that make the code more readable

#define output_low(port,pin) port &= ~(1<<pin)
#define output_high(port,pin) port |= (1<<pin)
#define set_input(portdir,pin) portdir &= ~(1<<pin)
#define set_output(portdir,pin) portdir |= (1<<pin)

#define FOSC    16000000
#define BAUD    9600
#define UBRR    FOSC/16/BAUD-1

volatile unsigned char u, oldu;

// ------------------------------------------- Set Receive Interrupt Enable ------------------------------------------- 

void setRXCIE_USART0()
{
    UCSR0B |= _BV(RXCIE0);
}

// -------------------------------------------- Initialize USART0 -----------------------------------------------------

void init_USART0 (unsigned int baud) {
	UBRR0 = baud;                       	// Set Baudrate
	UCSR0C = (3<<UCSZ00);               	// Character Size 8 bit
	UCSR0B |= _BV(RXEN0) | _BV(TXEN0);  	// Receiver and Transmitter Enable
}

// -------------------------------------------- Receive 1 byte  Data --------------------------------------------------

unsigned char receive_1byte_USART0 (void) {
	loop_until_bit_is_set(UCSR0A, RXC0);
	return UDR0;
}

// -------------------------------------------- Transmit 1 byte Data --------------------------------------------------

void transmit_1byte_USART0 (unsigned char data) {
	loop_until_bit_is_set(UCSR0A, UDRE0);
	UDR0 = data;
}

// -------------------------------------------- Interrupt handler -----------------------------------------------------

ISR(USART_RX_vect){ // USART RX interrupt
	volatile unsigned char c;
	c = UDR0;
	//transmit_1byte_USART0('w');
	//output_high(PORTB, LED1);
	//if (c<'5' && c>'0') 
	u = c;
}

// --------------------------------------------- Transmit String Data -------------------------------------------------

void transmit_str_USART0 (char *str) {
	while (*str != 0) {
		transmit_1byte_USART0(*str);
		*str++;
	}
}

// --------------------------------------------- Transmit Four-Digit Integer ------------------------------------------

void transmit_4digit_USART0 (int num) {
	unsigned char temp;
	int digit = 1000;

	while (digit != 0) {
		temp = num / digit;
		transmit_1byte_USART0('0'+temp);
		num -= (digit*temp);
		digit /= 10;
	}
}

// --------------------------------------------- Delays --------------------------------------------------------------

void delay_ms (uint16_t ms) {
	uint16_t delay_count = FOSC / 17500 * 0.1;
	volatile uint16_t i;
	while (ms != 0) {
		for (i=0; i != delay_count; i++);
		ms--;
	}
}

void delay_ns(uint16_t ms) {
	uint16_t delay_count = FOSC / 17500 * 0.01;
	volatile uint16_t i;
	while (ms != 0) {
		for (i=0; i != delay_count; i++);
		ms--;
	}
}

// --------------------------------------------- Sounds -------------------------------------------------------------

void beep (){
	for(uint8_t a=0; a<100; a++){
		output_high(PORTB, BEEPER);
		delay_ms(1);
		output_low(PORTB, BEEPER);
		delay_ms(1);
	}
}

void bleepBleepSound (){
	uint8_t a, b;
	for(b=1; b<40; b+=10) for(a=0; a<100; a++){
		output_high(PORTB, BEEPER);
		delay_ns(b);
		output_low(PORTB, BEEPER);
		delay_ns(b);
	}
}

// --------------------------------- LED MATRIX code ---------------------------------------------------------------

// define max7219 registers
uint8_t max7219_reg_noop        = 0x00;
uint8_t max7219_reg_digit0      = 0x01;
uint8_t max7219_reg_digit1      = 0x02;
uint8_t max7219_reg_digit2      = 0x03;
uint8_t max7219_reg_digit3      = 0x04;
uint8_t max7219_reg_digit4      = 0x05;
uint8_t max7219_reg_digit5      = 0x06;
uint8_t max7219_reg_digit6      = 0x07;
uint8_t max7219_reg_digit7      = 0x08;
uint8_t max7219_reg_decodeMode  = 0x09;
uint8_t max7219_reg_intensity   = 0x0a;
uint8_t max7219_reg_scanLimit   = 0x0b;
uint8_t max7219_reg_shutdown    = 0x0c;
uint8_t max7219_reg_displayTest = 0x0f;


void putByte(uint8_t data) {
	uint8_t i = 8;
	uint8_t mask;
	while(i > 0) {
		mask = 0x01 << (i - 1);      // get bitmask
		//digitalWrite( CLOCK, LOW);   // tick
		output_low(PORTB, CLOCK);	// tick
		if (data & mask){            // choose bit
			//digitalWrite(DATAIN, HIGH);// send 1
			output_high(PORTB, DATAIN);
		} else {
	 	     //digitalWrite(DATAIN, LOW); // send 0
			output_low(PORTB, DATAIN);
		}
 		//digitalWrite(CLOCK, HIGH);   // tock
		output_high(PORTB, CLOCK);
    		--i;                         // move to lesser bit
	}
}

void maxOne(uint8_t maxNr, uint8_t reg, uint8_t col) {    
	//maxOne is for adressing different MAX7219's, 
	//whilele having a couple of them cascaded
	int c = 0;
	//digitalWrite(load, LOW);  // begin     
	output_low(PORTB, LOAD);

	for ( c = INUSE; c > maxNr; c--) {
		putByte(0);    // means no operation
		putByte(0);    // means no operation
	}

	putByte(reg);  // specify register
	putByte(col);//((data & 0x01) * 256) + data >> 1); // put data 

	for ( c = maxNr-1; c >= 1; c--) {
		putByte(0);    // means no operation
		putByte(0);    // means no operation
	}

	//digitalWrite(load, LOW); // and load da shit
	output_low(PORTB, LOAD);
	//digitalWrite(load,HIGH); 
	output_high(PORTB, LOAD);
}

/*
void putPixel (uint8_t maxNr, uint8_t x, uint8_t y){
	//maxOne is for adressing different MAX7219's, 
	//whilele having a couple of them cascaded

	int c = 0;
	//digitalWrite(load, LOW);  // begin     
	output_low(PORTB, LOAD);

	for ( c = INUSE; c > maxNr; c--) {
		putByte(0);    // means no operation
		putByte(0);    // means no operation
	}

	putByte(y);  // specify register
	putByte(0x01 << (8-x));//((data & 0x01) * 256) + data >> 1); // put data 

	for ( c = maxNr-1; c >= 1; c--) {
		putByte(0);    // means no operation
		putByte(0);    // means no operation
	}

	//digitalWrite(load, LOW); // and load da shit
	output_low(PORTB, LOAD);
	//digitalWrite(load,HIGH); 
	output_high(PORTB, LOAD);
}
*/

// --------------------------------- Figures ----------------------------------------------------------------------

uint8_t	figure[5][4][4] = {	
				{
					{
					0b00011000,
					0b00011000,
					0b00000000,
					0b00000000
					},
					{
					0b00011000,
					0b00011000,
					0b00000000,
					0b00000000
					},
					{
					0b00011000,
					0b00011000,
					0b00000000,
					0b00000000
					},
					{
					0b00011000,
					0b00011000,
					0b00000000,
					0b00000000
					}
				},  
				{
					{
					0b00110000,
					0b00011000,
					0b00000000,
					0b00000000
					},
					{
					0b00010000,
					0b00110000,
					0b00100000,
					0b00000000
					},
					{
					0b00110000,
					0b00011000,
					0b00000000,
					0b00000000
					},
					{
					0b00010000,
					0b00110000,
					0b00100000,
					0b00000000
					}
				}, 
				{
					{
					0b00000000,
					0b00111000,
					0b00001000,
					0b00000000
					},
					{
					0b00011000,
					0b00010000,
					0b00010000,
					0b00000000
					},
					{
					0b00100000,
					0b00111000,
					0b00000000,
					0b00000000
					},
					{
					0b00010000,
					0b00010000,
					0b00110000,
					0b00000000
					}
				}, 
				{
					{
					0b00010000,
					0b00111000,
					0b00000000,
					0b00000000
					},
					{
					0b00010000,
					0b00110000,
					0b00010000,
					0b00000000
					},
					{
					0b00000000,
					0b00111000,
					0b00010000,
					0b00000000
					},
					{
					0b00010000,
					0b00011000,
					0b00010000,
					0b00000000
					}
				}, 
				{
					{
					0b00000000,
					0b00111100,
					0b00000000,
					0b00000000
					},
					{
					0b00010000,
					0b00010000,
					0b00010000,
					0b00010000
					},
					{
					0b00000000,
					0b00111100,
					0b00000000,
					0b00000000
					},
					{
					0b00010000,
					0b00010000,
					0b00010000,
					0b00010000
					}
				}
			  };

 
// ----------------------------------------- Init variables ------------------------------------------------------------

uint8_t	screen[ INUSE*8 + 1 ];

uint8_t currentFigure = 4;
uint8_t currentTurn = 0;
uint8_t currentY = 0;
uint8_t currentX = STARTX;
uint32_t timer = 0;
uint8_t randomDigit = 0;
uint8_t prewRandomDigit;
uint8_t score = 1; 

// ------------------------------------------ Game logic ---------------------------------------------------------------

uint8_t moveLine(uint8_t L, uint8_t X){
	if (X <= 4 ) L <<= 4 - X; else L >>= X - 4;
	return L;
}

void redrawScreen (void) {
	uint8_t lineToPlace = 0b00000000;
	for (uint8_t matrix=0;  matrix < INUSE; matrix++) for (uint8_t line=0; line<8; line++){
		if (matrix*8 + line >= currentY && matrix*8 + line < currentY+4) lineToPlace = figure[currentFigure][currentTurn][matrix*8 +line - currentY]; else lineToPlace = 0b00000000;
		//lineToPlace = 0b11111111; else lineToPlace = 0b00000000;
		//if (currentX <= 4 ) lineToPlace <<= 4 - currentX; else lineToPlace >>= currentX - 4;
		lineToPlace = moveLine(lineToPlace, currentX);
		maxOne(matrix+1, line+1, screen[matrix*8 + line] | lineToPlace );	
	}
}

uint8_t checkDown (void){
	uint8_t lineToPlace = 0b00000000;
	for (uint8_t line = 0; line<4; line ++){
		lineToPlace = figure[currentFigure][currentTurn][line];
		lineToPlace = moveLine(lineToPlace, currentX);
		if ( (lineToPlace & screen[ currentY+1 + line]) != 0) return 1;
	}
	return 0;
}

uint8_t checkStart (void){
	uint8_t lineToPlace = 0b00000000;
	for (uint8_t line = 0; line<4; line ++){
		lineToPlace = figure[currentFigure][currentTurn][line];
		lineToPlace = moveLine(lineToPlace, currentX);
		if ( (lineToPlace & screen[ currentY + line]) != 0) return 1;
	}
	return 0;
}

uint8_t checkLeft (void){
	uint8_t lineToPlace = 0b00000000;
	uint8_t tmpLineToPlace;
	for (uint8_t line = 0; line<4; line ++){
		lineToPlace = figure[currentFigure][currentTurn][line];
		tmpLineToPlace = moveLine(lineToPlace, currentX);
		if ( (tmpLineToPlace & 0b10000000) != 0) return 1;
		lineToPlace = moveLine(lineToPlace, currentX - 1);
		if ( (lineToPlace & screen[ currentY + line]) != 0) return 1;
	}
	return 0;
}

uint8_t checkRight (void){
	uint8_t lineToPlace = 0b00000000;
	uint8_t tmpLineToPlace;
	for (uint8_t line = 0; line<4; line ++){
		lineToPlace = figure[currentFigure][currentTurn][line];		
		tmpLineToPlace = moveLine(lineToPlace, currentX);
		if ( (tmpLineToPlace & 0b00000001) != 0) return 1;
		lineToPlace = moveLine(lineToPlace, currentX + 1);
		if ( (lineToPlace & screen[ currentY + line]) != 0) return 1;
	}
	return 0;
}

uint8_t checkTurn (void){
	uint8_t lineToPlace = 0b00000000;
	uint8_t testTurn; 
	if (currentTurn < 3) testTurn =  currentTurn + 1; else testTurn = 0;
	for (uint8_t line = 0; line<4; line ++){
		lineToPlace = figure[currentFigure][testTurn][line];
		lineToPlace = moveLine(lineToPlace, currentX);
		//if ( (lineToPlace & 0b00000001) != 0) return 1; 
		if (currentFigure == 1 && currentX == 8) return 1;
		if (currentFigure == 2 && currentX == 1) return 1;
		if (currentFigure == 2 && currentX == 8) return 1;
		if (currentFigure == 3 && currentX == 1) return 1;
		if (currentFigure == 3 && currentX == 8) return 1;   
		if (currentFigure == 4 && currentX == 2) return 1;
		if (currentFigure == 4 && currentX == 7) return 1; 
		if ( (lineToPlace & screen[ currentY + line]) != 0) return 1;
	}
	return 0;
}

void checkFullLine (void) {
	uint8_t a = 0;
	uint8_t matrix = currentY / (INUSE*8);
	uint8_t remainder = currentY % (INUSE*8);
	for (uint8_t line = 0; line<4; line ++) if (line + currentY < INUSE*8) {
		if (screen[line + currentY] == 0b11111111) {
			for (a = 0; a<3; a++) {
				
				maxOne(matrix + 1, line + remainder +1, 0b00000000 );
				//screen[line + currentY] = 0b11111111;	
				//redrawScreen ();
				delay_ms(1000);
				maxOne(matrix + 1, line + remainder +1, 0b11111111 );	
				//screen[line + currentY] = 0b00000000;
				//redrawScreen ();
				delay_ms(1000);
			}
			for (a = line + currentY; a>0; a--) screen[a] = screen[a-1];
			bleepBleepSound ();
			transmit_str_USART0("Score: "); 
			transmit_4digit_USART0(score++);
			transmit_str_USART0("        \r");
		}
	}
}

void gameOver () {
	uint8_t ornament[3] = {0b00100100, 0b10010010, 0b01001001};
	uint8_t offset = 0;
	for (uint8_t matrix=0;  matrix < INUSE; matrix++) for (uint8_t line=0; line<8; line++){
		maxOne(matrix + 1, line + 1, 0b11111111 );
		delay_ms(500);
	}
	for (uint8_t matrix=0;  matrix < INUSE; matrix++) for (uint8_t line=0; line<8; line++){
		maxOne(matrix + 1, line + 1, 0b00000000 );
		screen[ matrix*8 + line] = 0b00000000;
		delay_ms(500);
	}

	while (u == 0) {
		for (uint8_t matrix=0;  matrix < INUSE; matrix++) for (uint8_t line=0; line<8; line++){
			maxOne(matrix + 1, line + 1, ornament[(line+offset) % 3] );
		}
		if (offset < 2) offset ++; else offset = 0;
		delay_ms(1000);
	}
	
	score = 1;
	transmit_str_USART0("Score: 0000        \r");

	currentFigure = (uint8_t)((uint16_t)timer % (uint16_t)5);
	currentTurn = 0;
	
}

void stopFigure (void) {
	uint8_t lineToPlace = 0b00000000;
	for (uint8_t line = 0; line<4; line ++) {
		lineToPlace = figure[currentFigure][currentTurn][line];
		lineToPlace = moveLine(lineToPlace, currentX);
		screen[line + currentY] |= lineToPlace;
	}
	checkFullLine ();
	currentY = 0;
	currentTurn = 0;
	currentX = STARTX;
	currentFigure = randomDigit;

	if (checkStart () != 1) redrawScreen (); else gameOver();
}

// --------------------------------- MAIN -----------------------------------------------------------------------

int main(void) {

	// initialize the direction of PORTD to be output

	set_output(DDRB, DATAIN);  
	set_output(DDRB, LOAD);
	set_output(DDRB, CLOCK);
	set_output(DDRB, BEEPER);

	// initiation of the max 7219
	for (uint8_t matrix=0; matrix < INUSE; matrix++) {
		maxOne(matrix+1, max7219_reg_scanLimit, 0x07);      
		maxOne(matrix+1, max7219_reg_decodeMode, 0x00);			// using a led matrix, not digits
		maxOne(matrix+1, max7219_reg_shutdown, 0x01);			// not in shutdown mode
		maxOne(matrix+1, max7219_reg_displayTest, 0x00);		// no display test
		for (uint8_t line=0; line<8; line ++) maxOne(matrix+1, line+1, 0);	// empty registers, turn all LEDs off 
		maxOne(matrix+1, max7219_reg_intensity, 0x0f & 0x0f);		// the first 0x0f is the value you can set (range: 0x00 to 0x0f)
	}
                                                  	
	init_USART0(UBRR);  // initialize USART0
	setRXCIE_USART0();
	sei();

	screen[ INUSE*8 ] = 0b11111111;	 					// bottom border for checking
	currentFigure = (uint8_t)((uint16_t)timer % (uint16_t)RAND_MAX);
	transmit_str_USART0("Score: 0000        \r");

	while (1) {
		if (timer % 20000 == 0) {
			redrawScreen ();
			if (checkDown() != 1) currentY ++; else {
				if (prewRandomDigit == randomDigit) randomDigit++;
				if (randomDigit > RAND_MAX+1) randomDigit = 0;
				prewRandomDigit	= randomDigit;
				stopFigure ();
			}
		}
		timer ++;
		if (u != oldu){
			oldu = u;
			randomDigit = (uint8_t)((uint16_t)timer % (uint16_t)RAND_MAX);	
			//transmit_1byte_USART0(timer);
			//transmit_4digit_USART0(u);	
			if (67 == u) {
				//transmit_str_USART0(" RIGHT \r");
				if (checkRight () != 1) currentX ++;
				redrawScreen ();
				beep ();
			}
			if (68 == u) {
				//transmit_str_USART0(" LEFT  \r");
				if (checkLeft () != 1) currentX --;
				redrawScreen ();
				beep ();
			}
			if (66 == u) {
				//transmit_str_USART0(" DOWN  \r");
				if (checkDown() != 1) currentY ++; 
				redrawScreen ();
				beep ();
			}
			if (65 == u) {
				//transmit_str_USART0(" UP    \r");
				if (checkTurn () != 1) { 
					if (currentTurn < 3) currentTurn ++; else currentTurn = 0;
					redrawScreen ();
					beep ();
				}
			}
			u = 0;
		}
	}

}

